﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace iNet.Media.Video
{
	/// <summary>
	/// ISO Base Media File Format (ISO/IEC 14496-12) 內容讀取器。
	/// </summary>
	public class IsoBaseMediaFileReader
	{
		#region BoxContainerInfo
		class BoxContainerInfo
		{
			public long FirstBoxOffset;
			public long FooterSize;
		}
		#endregion
		#region 靜態欄位
		static readonly Dictionary<uint, BoxContainerInfo> _ContainerBoxTypes = new Dictionary<uint, BoxContainerInfo>();
		#endregion
		#region 欄位
		BoxContainerInfo _CurrentBoxContainerInfo;
		byte[] _CurrentBoxData;
		long? _CurrentBoxDataSize;
		uint? _CurrentBoxType;
		string _CurrentBoxTypeString;
		readonly long? _DataLength;
		bool _HasError;
		IsoBaseMediaFileReader _InnerReader;
		long _ReadLength;
		readonly Stream _Stream;
		#endregion
		#region 型別建構子
		static IsoBaseMediaFileReader()
		{
			// avc1
			_ContainerBoxTypes.Add(0x61766331, new BoxContainerInfo() { FirstBoxOffset = 78 });
			// dinf
			_ContainerBoxTypes.Add(0x64696e66, new BoxContainerInfo());
			// gshh
			_ContainerBoxTypes.Add(0x67736868, new BoxContainerInfo());
			// gspm
			_ContainerBoxTypes.Add(0x6773706d, new BoxContainerInfo());
			// gspu
			_ContainerBoxTypes.Add(0x67737075, new BoxContainerInfo());
			// gssd
			_ContainerBoxTypes.Add(0x67737364, new BoxContainerInfo());
			// gsst
			_ContainerBoxTypes.Add(0x67737374, new BoxContainerInfo());
			// gstd
			_ContainerBoxTypes.Add(0x67737464, new BoxContainerInfo());
			// ilst
			_ContainerBoxTypes.Add(0x696c7374, new BoxContainerInfo());
			// meta
			_ContainerBoxTypes.Add(0x6d657461, new BoxContainerInfo() { FirstBoxOffset = 4 });
			// mdia
			_ContainerBoxTypes.Add(0x6d646961, new BoxContainerInfo());
			// minf
			_ContainerBoxTypes.Add(0x6d696e66, new BoxContainerInfo());
			// moov
			_ContainerBoxTypes.Add(0x6d6f6f76, new BoxContainerInfo());
			// mp4a
			_ContainerBoxTypes.Add(0x6d703461, new BoxContainerInfo() { FirstBoxOffset = 28, FooterSize = 8 });
			// stbl
			_ContainerBoxTypes.Add(0x7374626c, new BoxContainerInfo());
			// stsd
			_ContainerBoxTypes.Add(0x73747364, new BoxContainerInfo() { FirstBoxOffset = 8 });
			// trak
			_ContainerBoxTypes.Add(0x7472616b, new BoxContainerInfo());
			// udta
			_ContainerBoxTypes.Add(0x75647461, new BoxContainerInfo());
		}
		#endregion
		#region 建構子
		/// <summary>
		/// 初始化新的 IsoBaseMediaFileReader 執行個體。
		/// </summary>
		/// <param name="stream">提供媒體資料的資料流。</param>
		/// <exception cref="ArgumentNullException">stream 為 Null (Visual Basic 為 Nothing)。</exception>
		public IsoBaseMediaFileReader(Stream stream)
			: this(stream, null)
		{ }
		/// <summary>
		/// 初始化新的 IsoBaseMediaFileReader 執行個體。
		/// </summary>
		/// <param name="stream">提供媒體資料的資料流。</param>
		/// <param name="dataLength">媒體資料位元組長度，Null (Visual Basic 為 Nothing) 表示可以讀取至資料流末端。</param>
		/// <exception cref="ArgumentNullException">stream 為 Null (Visual Basic 為 Nothing)。</exception>
		/// <exception cref="ArgumentOutOfRangeException">dataLength 為負數。</exception>
		public IsoBaseMediaFileReader(Stream stream, long? dataLength)
		{
			//確認參數
			if (stream == null)
				throw new ArgumentNullException("stream");
			if (dataLength != null && dataLength.Value < 0)
				throw new ArgumentOutOfRangeException("dataLength");

			//初始化欄位
			_Stream = stream;
			_DataLength = dataLength;
		}
		#endregion
		#region CurrentBoxDataSize
		/// <summary>
		/// 取得目前讀取 Box 中的資料位元組大小。
		/// </summary>
		public long? CurrentBoxDataSize
		{
			get
			{
				if (_InnerReader != null)
					return _InnerReader.CurrentBoxDataSize;
				return _CurrentBoxDataSize;
			}
		}
		#endregion
		#region CurrentBoxType
		/// <summary>
		/// 取得目前讀取 Box 的類型。
		/// </summary>
		public uint? CurrentBoxType
		{
			get
			{
				if (_InnerReader != null)
					return _InnerReader.CurrentBoxType;
				return _CurrentBoxType;
			}
		}
		#endregion
		#region CurrentBoxTypeString
		/// <summary>
		/// 取得目前讀取 Box 的類型識別字串。
		/// </summary>
		public string CurrentBoxTypeString
		{
			get
			{
				if (_InnerReader != null)
					return _InnerReader.CurrentBoxTypeString;
				return _CurrentBoxTypeString;
			}
		}
		#endregion
		#region HasError
		/// <summary>
		/// 取得值以確認讀取時是否發生錯誤。
		/// </summary>
		public bool HasError
		{
			get
			{
				return _HasError;
			}
		}
		#endregion
		#region IsContainerBox
		/// <summary>
		/// 取得值以確認目前讀取的 Box 是否為其他 Box 的容器。
		/// </summary>
		public bool IsContainerBox
		{
			get
			{
				if (_CurrentBoxType != null)
					return _ContainerBoxTypes.ContainsKey(_CurrentBoxType.Value);
				return false;
			}
		}
		#endregion
		#region ReadBoxData
		/// <summary>
		/// 讀取目前 Box 中的資料。
		/// </summary>
		/// <returns>Box 中的資料，Null (Visual Basic 為 Nothing) 表示無法讀取資料。</returns>
		public byte[] ReadBoxData()
		{
			//讀取子 Box 資料
			if (_InnerReader != null)
				return _InnerReader.ReadBoxData();

			//確認狀態
			if (_HasError || _CurrentBoxType == null)
				return null;

			//讀取資料
			if (_CurrentBoxData == null)
			{
				try
				{
					if (_CurrentBoxDataSize != null)
					{
						if (_CurrentBoxDataSize.Value <= int.MaxValue)
						{
							if (_DataLength == null || (_ReadLength + _CurrentBoxDataSize.Value) <= _DataLength.Value)
							{
								_CurrentBoxData = new byte[_CurrentBoxDataSize.Value];
								byte[] buffer = new byte[Math.Min(4096, _CurrentBoxData.Length)];
								int restByteCount = _CurrentBoxData.Length;
								while (restByteCount > 0)
								{
									int readCount = _Stream.Read(buffer, 0, Math.Min(restByteCount, buffer.Length));
									if (readCount == 0)
									{
										_CurrentBoxData = null;
										_HasError = true;
										break;
									}
									Array.Copy(buffer, 0, _CurrentBoxData, (_CurrentBoxData.Length - restByteCount), readCount);
									restByteCount -= readCount;
								}
							}
							else
								_HasError = true;
						}
					}
					else
					{
						if (_DataLength == null)
						{
							List<byte> fullDataBUffer = new List<byte>();
							byte[] buffer = new byte[4096];
							int readCount = _Stream.Read(buffer, 0, buffer.Length);
							while (readCount > 0)
							{
								if (readCount == buffer.Length)
									fullDataBUffer.AddRange(buffer);
								else
								{
									byte[] subBuffer = new byte[readCount];
									Array.Copy(buffer, 0, subBuffer, 0, readCount);
									fullDataBUffer.AddRange(subBuffer);
								}
								readCount = _Stream.Read(buffer, 0, buffer.Length);
							}
							_CurrentBoxData = fullDataBUffer.ToArray();
						}
						else
							_HasError = true;
					}
				}
				catch
				{
					_HasError = true;
					_CurrentBoxData = null;
				}
			}

			//完成
			return _CurrentBoxData;
		}
		#endregion
		#region ReadNextBox
		/// <summary>
		/// 讀取下一個 Box。
		/// </summary>
		/// <param name="skipInnerBoxes">是否要忽略內嵌在 Box 資料中的子 Box。</param>
		/// <returns>True 表示讀取成功，False 表示失敗。</returns>
		public bool ReadNextBox(bool skipInnerBoxes)
		{
			//確認狀態
			if (_HasError)
				return false;
			if (_DataLength != null && _ReadLength >= _DataLength.Value)
				return false;

			//讀取下一個 Box
			while (true)
			{
				//讀取下一個內層 Box
				if (_InnerReader != null)
				{
					//讀取下一個內層 Box
					if (_InnerReader.ReadNextBox(skipInnerBoxes))
						return true;

					//恢復狀態
					try
					{
						if (_InnerReader._DataLength != null)
						{
							if (_InnerReader._Stream == _Stream)
							{
								_ReadLength += _InnerReader._DataLength.Value;
								if (_CurrentBoxContainerInfo != null && _CurrentBoxContainerInfo.FooterSize > 0 && !this.SeekStreamPosition(_CurrentBoxContainerInfo.FooterSize))
								{
									_HasError = true;
									return false;
								}
								if (_DataLength != null && _ReadLength >= _DataLength.Value)
									return false;
							}
							else if (_CurrentBoxData != null)
								_ReadLength += _CurrentBoxData.Length;
							else
							{
								_HasError = true;
								return false;
							}
						}
						else
							return false;
					}
					finally
					{
						_InnerReader = null;
						_CurrentBoxType = null;
						_CurrentBoxTypeString = null;
						_CurrentBoxContainerInfo = null;
					}
				}

				//移動至下一個 Box
				if (_CurrentBoxType != null)
				{
					if (!skipInnerBoxes && _ContainerBoxTypes.TryGetValue(_CurrentBoxType.Value, out _CurrentBoxContainerInfo))
					{
						//取得子 Box 總長度
						long? innerDataLength = (_CurrentBoxDataSize.Value > 0 ? (_CurrentBoxDataSize - _CurrentBoxContainerInfo.FirstBoxOffset - _CurrentBoxContainerInfo.FooterSize) : null);

						//移動至子 Box 開頭
						Stream innerStream;
						if (_CurrentBoxData == null)
						{
							if (_CurrentBoxContainerInfo.FirstBoxOffset > 0 && !this.SeekStreamPosition(_CurrentBoxContainerInfo.FirstBoxOffset))
							{
								_HasError = true;
								return false;
							}
							innerStream = _Stream;
						}
						else
						{
							innerStream = new MemoryStream(_CurrentBoxData);
							if (_CurrentBoxContainerInfo.FirstBoxOffset > 0)
							{
								if (innerStream.Length >= _CurrentBoxContainerInfo.FirstBoxOffset)
									innerStream.Seek(_CurrentBoxContainerInfo.FirstBoxOffset, SeekOrigin.Current);
								else
									innerStream = null;
							}
						}

						//建立子 Box 讀取器
						if (innerStream != null)
						{
							_InnerReader = new IsoBaseMediaFileReader(innerStream, innerDataLength);
							continue;
						}
					}
					if (_CurrentBoxDataSize == null || (_CurrentBoxData == null && !this.SeekStreamPosition(_CurrentBoxDataSize.Value)))
						return false;
					if (_CurrentBoxData != null)
					{
						_ReadLength += _CurrentBoxData.Length;
						_CurrentBoxData = null;
					}
				}

				//確認狀態
				if (_DataLength != null)
				{
					if (_ReadLength == _DataLength.Value)
						return false;
					if ((_ReadLength + 8) > _DataLength.Value)
					{
						_HasError = true;
						return false;
					}
				}

				//讀取 Box 資訊
				byte[] buffer = new byte[8];
				int readCount = _Stream.Read(buffer, 0, 8);
				if (readCount == 0)
					return false;
				if (readCount < 8)
				{
					_HasError = true;
					return false;
				}
				_ReadLength += 8;

				//取得 Box 類型
				_CurrentBoxType = BitConverter.BigEndian.ToUInt32(buffer, 4);
				_CurrentBoxTypeString = Encoding.ASCII.GetString(buffer, 4, 4);

				//取得 Box 大小
				uint boxSize = BitConverter.BigEndian.ToUInt32(buffer);
				if (boxSize >= 8)
					_CurrentBoxDataSize = (boxSize - 8);
				else if (boxSize == 1)
				{
					if (_DataLength != null && (_ReadLength + 8) > _DataLength.Value)
					{
						_HasError = true;
						return false;
					}
					if (_Stream.Read(buffer, 0, 8) < 8)
					{
						_HasError = true;
						return false;
					}
					_ReadLength += 8;
					_CurrentBoxDataSize = BitConverter.BigEndian.ToInt64(buffer);
					if (_CurrentBoxDataSize.Value >= 16)
						_CurrentBoxDataSize = (_CurrentBoxDataSize.Value - 16);
					else
					{
						_HasError = true;
						return false;
					}
				}
				else if (boxSize == 0)
					_CurrentBoxDataSize = null;
				else
				{
					_HasError = true;
					return false;
				}

				//完成
				return true;
			}
		}
		#endregion
		#region SeekStreamPosition
		bool SeekStreamPosition(long offsetToCurrent)
		{
			try
			{
				//確認是否超出邊界
				bool outOfBounds = (_DataLength != null && _ReadLength + offsetToCurrent > _DataLength.Value);
				if (outOfBounds)
					offsetToCurrent = (_DataLength.Value - _ReadLength);

				//向後移動讀取位置
				if (_Stream.CanSeek)
					_Stream.Seek(offsetToCurrent, SeekOrigin.Current);
				else
				{
					byte[] buffer = new byte[4096];
					long readCount = 0;
					while (readCount < offsetToCurrent)
					{
						int count = _Stream.Read(buffer, 0, (int)Math.Min(buffer.Length, (offsetToCurrent - readCount)));
						readCount += count;
					}
				}
				_ReadLength += offsetToCurrent;
				return !outOfBounds;
			}
			catch
			{
				return false;
			}
		}
		#endregion
	}
}
